<template>
  <div class="chat">
    <gptHistory
      :records="historyList"
      :isLoading="isLoading"
      :historyId="historyId"
      :isCollapse="isCollapse"
      @addEmit="addEmitFn"
      @previewEmit="previewEmitFn"
      @deleteEmit="deleteEmitFn"
      @contentEmit="contentEmitFn"
    />
    <div class="chat-right">
      <div
        class="chat-right-collapsed"
        :style="{ background: !isCollapse ? '#fff' : '', display: 'none' }"
        @click="isCollapse = !isCollapse"
      >
        <i v-if="isCollapse" class="el-icon-arrow-left"></i>
        <i v-if="!isCollapse" class="el-icon-arrow-right"></i>
      </div>
      <div class="chat-content">
        <div class="chat-content-session">
          <gptEmpty @sentCon="sentConFn" v-if="chatList.length === 0" />
          <div v-for="(item, index) in chatList" :key="index">
            <div
              v-if="item.role === 'user'"
              class="chat-content-session-caption"
            >
              <div>{{ item.content }}</div>
              <img src="@/assets/images/labor/head-portrait.png" alt="" />
            </div>
            <div v-if="item.role === 'assistant'">
              <div
                class="chat-content-session-answer"
                v-if="index !== 0 && isLoading && index === chatList.length - 1"
              >
                <div class="chat-content-session-answer-box">
                  <img src="@/assets/images/labor/robot-ai.png" alt="" />
                  <v-md-preview
                    v-if="msg.length > 0"
                    :text="msg"
                  ></v-md-preview>
                  <div class="loading" v-else>
                    <img src="@/assets/images/labor/time.png" alt="" />
                    <span class="loading-text">思考中</span>
                    <img
                      class="rotating-image"
                      src="@/assets/images/labor/loading.png"
                      alt=""
                    />
                  </div>
                </div>
                <div
                  class="chat-content-session-answer-operat"
                  v-if="isLoading && msg.length > 0"
                >
                  <!-- <div class="chat-content-session-answer-operat-btn" @click="stopHandler()">停止输出</div> -->
                </div>
              </div>
              <div class="chat-content-session-answer" v-else>
                <div class="chat-content-session-answer-box">
                  <img src="@/assets/images/labor/robot-ai.png" alt="" />
                  <v-md-preview :text="item.content"></v-md-preview>
                </div>
                <div class="chat-content-session-answer-operat">
                  <div
                    class="chat-content-session-answer-operat-btn"
                    @click="anewConFn(index)"
                  >
                    重新生成
                  </div>
                  <div class="chat-content-session-answer-operat-chose">
                    <img src="@/assets/images/labor/endorse-icon.png" alt="" />
                    <div></div>
                    <img src="@/assets/images/labor/oppose-icon.png" alt="" />
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
        <div class="anew" v-if="isLoading && msg.length > 0">
          <div class="anew-box" @click="stopHandler()">
            <img src="@/assets/images/labor/stop-play.png" alt="" />
            <span>停止输出</span>
          </div>
        </div>
        <gptInput
          @sentCon="sentConFn"
          :localChatCon="localChatCon"
          :isLoading="isLoading"
        />
      </div>
    </div>
  </div>
</template>

<script>
import api from "@/api/request";
import { fetchEventSource } from "@microsoft/fetch-event-source";
import moment from "moment";
import { cloneDeep } from "lodash";
import { randomUUID } from "@utils/utils";
import { Typewriter } from "./typewriter";

import gptEmpty from "./components/gptEmpty.vue";
import gptHistory from "./components/gptHistory.vue";
import gptInput from "./components/gptInput.vue";
export default {
  name: "ChatGpt",
  components: {
    gptEmpty,
    gptHistory,
    gptInput,
  },
  data() {
    return {
      aiHeader: "ZU4weWpzU1huV0NnekxiekZrQmU4OUpJOWNCbmwvU0xaR3NB",
      promptUuid: "16c9caf1-835f-44ab-93da-f4fc3faf18c3-1708322034471",
      isMatch: false,
      msg: "",
      historyList: [],
      historyId: "",
      chatList: [],
      sessionList: [],
      localChatCon: "",
      isLoading: false,
      sessionId: "",
      content: "",
      isCollapse: false,
      ctrlAbout: null,
    };
  },
  mounted() {
    this.sessionId = randomUUID();
    // this.getAiRecords();
  },
  methods: {
    scrollToBottom() {
      const chatRef = document.querySelectorAll(".chat-content-session");
      chatRef[0].scrollTo({
        top: chatRef[0].scrollHeight,
        left: 0,
        behavior: "smooth",
      });
    },
    contentEmitFn(val) {
      this.content = val;
      this.getAiRecords();
    },
    // 新建会话
    addEmitFn() {
      this.sessionId = randomUUID();
      this.historyId = "";
      this.chatList = [];
      this.sessionList = [];
    },
    // 查看某条会话
    async previewEmitFn(row) {
      const res = await api.queryAiDetail({ id: row.id });
      if (res.code === 0) {
        this.sessionId = row.sessionId;
        this.historyId = row.id;
        this.chatList = res.data || [];
        this.sessionList = res.data.filter((opt) => opt.role === "user") || [];
        this.$nextTick(() => {
          this.scrollToBottom();
        });
      } else {
        this.$message.error(res.msg);
      }
    },
    // 删除会话
    async deleteEmitFn(row) {
      const res = await api.delChatHistory(row.id);
      if (res.code === 0) {
        if (row.id === this.historyId) {
          this.sessionId = "";
          this.historyId = "";
          this.chatList = [];
          this.sessionList = [];
        }
        this.getAiRecords();
      } else {
        this.$message.error(res.msg);
      }
    },

    // 查询历史记录
    async getAiRecords() {
      const res = await api.queryAiRecords({
        content: this.content,
      });
      if (res.code === 0) {
        this.historyList =
          res.data.map((item) => ({ ...item, visible: null })) || [];
      } else {
        this.$message.error(res.msg);
      }
    },

    // 保存会话记录
    async saveSessionMessage(isGetMsg = false) {
      // const messages = cloneDeep(this.chatList);
      // const params = {
      //  promptUuid: this.promptUuid,
      //  sessionId: this.sessionId || randomUUID(),
      //  messages,
      // };
      // try {
      //   await api.addChatHistory(params);
        if (isGetMsg) {
          this.isLoading = false;
      //     await this.getAiRecords();
        }
      // } catch (e) {
      //   console.error(e);
      //  }
    },

    async askQuestion() {
      const chatItem = {
        role: "user",
        content: this.localChatCon,
        dateTime: moment().format("YYYY-MM-DD HH:mm:ss"),
        type: "success",
      };
      this.chatList.push(chatItem);
      this.sessionList?.push(chatItem);
      this.chatCompletions(this.localChatCon);
      this.localChatCon = "";
    },
    // 接受数据流
    async chatCompletions(val) {
      let that = this;
      this.msg = "";
      let messages;
      messages = cloneDeep(this.chatList);
      const params = {
        // promptUuid: this.promptUuid,
        // query: val,
        // messages,
        model: 'deepseek-r1:14b',
        prompt: val,
        stream: true
      };
      this.isLoading = true;
      this.chatList.push({
        role: "assistant",
        content: "",
        dateTime: "",
        type: "success",
      });

      this.$nextTick(() => {
        this.scrollToBottom();
      });
      const url = window.location.origin;
      this.ctrlAbout = new AbortController();
      let count = 0;
      const typewriter = new Typewriter((str) => {
        this.msg += str || "";
      });
      if ("EventSource" in window) {
        await fetchEventSource(
          `${url}/api/v1/ai/askingQuestions3`,
          {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              Accept: "*/*",
              // "x-chat-inner-identity": that.aiHeader,
            },
            signal: that.ctrlAbout.signal,
            body: JSON.stringify(params),
            onmessage(event) {
              const eventData = JSON.parse(event.data);
              if (!eventData.done) {
                count++;
                if (count === 1) {
                  typewriter.start();
                }
                if (eventData.response == '\u003cthink\u003e') {
                  // typewriter.add("<div class='think'>");
                  that.msg += "<div class='parent-think'><div class='think'>";
                } else if (eventData.response == '\u003c/think\u003e') {
                  typewriter.add("</div></div>");
                }
                else {
                  typewriter.add(eventData.response);
                }
                that.$nextTick(() => {
                  that.scrollToBottom();
                });
              } else {
                console.log(that.msg);
                typewriter.done();
              }
            },
            onerror(err) {
              debugger
              that.msg =
                "很抱歉，机器人网络出现了一些问题，无法响应您的请求。请稍后再试。";
              that.$nextTick(() => {
                that.scrollToBottom();
              });
              that.chatList[that.chatList.length - 1].type = "error";
              that.chatList[that.chatList.length - 1].content = that.msg;
              that.chatList[that.chatList.length - 1].dateTime =
                moment().format("YYYY-MM-DD HH:mm:ss");
              that.saveSessionMessage(true);
              throw err;
            },
          }
        );
      } else {
        console.error("error");
      }
      this.chatList[this.chatList.length - 1].content = this.msg;
      this.chatList[this.chatList.length - 1].dateTime = moment().format(
        "YYYY-MM-DD HH:mm:ss"
      );
      await this.saveSessionMessage(true);
    },

    sentConFn(val) {
      this.localChatCon = val;
      this.askQuestion();
    },

    anewConFn(index) {
      this.localChatCon = this.chatList[index - 1].content;
      this.askQuestion();
    },

    async stopHandler() {
      this.ctrlAbout.abort();
      const typewriter = new Typewriter((str) => {
        this.msg += str || "";
      });
      typewriter.done();
      this.chatList[this.chatList.length - 1].content = this.msg;
      this.chatList[this.chatList.length - 1].dateTime = moment().format(
        "YYYY-MM-DD HH:mm:ss"
      );
      await this.saveSessionMessage(true);
      this.$nextTick(() => {
        this.scrollToBottom();
      });
    },
  },
};
</script>

<style lang="less" scoped>
.anew {
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 10px 0 0;
  .anew-box {
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid #d9d9d9;
    border-radius: 18px;
    height: 36px;
    width: 100px;
    cursor: pointer;
    &:hover {
      background: #f7f7f7;
    }
    > img {
      width: 16px;
      margin-right: 6px;
    }
    span {
      font-family: PingFang SC;
      font-size: 14px;
      font-weight: 400;
      line-height: 19.6px;
      color: #000;
    }
  }
}
.loading {
  width: 110px !important;
  height: 46px !important;
  display: flex;
  align-items: center;
  justify-content: center;
  .loading-text {
    font-family: PingFang SC;
    font-size: 14px;
    font-weight: 400;
    color: #0078c9;
    margin: 0 5px;
  }
  > img {
    width: 12px !important;
  }
}
.rotating-image {
  animation: rotate 1s linear infinite;
}

@keyframes rotate {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}
.chat {
  display: flex;
  background: #fff;
  border-radius: 5px;
  height: calc(100vh - 20px);
  margin: 10px;
}
.chat-right {
  flex: 1;
  padding: 0 100px 19px;
  position: relative;
  .chat-right-collapsed {
    position: absolute;
    top: 50%;
    left: 10px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 49px;
    background: #fafafa;
    border-radius: 20px;
    cursor: pointer;
    > i {
      font-size: 18px;
      font-weight: bold;
      color: rgba(167, 167, 167, 0.9);
    }
  }
}
.chat-content {
  height: 100%;
  display: flex;
  flex-direction: column;
  .chat-content-session {
    flex: 1;
    overflow-y: auto;
    padding: 10px 30px 10px;
  }
}
.chat-content-session-caption {
  display: flex;
  justify-content: end;
  margin: 0 0 24px 50px;
  > img {
    flex-shrink: 0;
    width: 30px;
    height: 30px;
    margin-left: 17px;
  }
  > div {
    display: inline-flex;
    align-items: center;
    justify-content: flex-end;
    font-family: PingFang SC;
    font-size: 14px;
    font-weight: 400;
    line-height: 19.6px;
    padding: 14px;
    color: #000;
    box-shadow: 0px 6px 30px 5px #0000000d;
    background: #e8f3fd;
    border-radius: 20px 0 20px 20px;
  }
}
.chat-content-session-answer {
  .chat-content-session-answer-box {
    display: flex;
    margin: 0 45px 14px 0;
    > img {
      width: 70px;
      height: 53px;
      margin: -20px 0px 0 -20px;
    }
    > div {
      // max-width: 869px;
      background-color: #fff;
      padding: 15px;
      border-radius: 10px;
      font-family: PingFang SC;
      font-size: 14px;
      font-weight: 400;
      line-height: 19.6px;
      color: #000;
      box-shadow: 0px 6px 30px 5px #0000000d;
    }
  }
  .chat-content-session-answer-operat {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 100px 0 60px;
    margin-bottom: 24px;
    .chat-content-session-answer-operat-btn {
      font-family: PingFang SC;
      font-size: 14px;
      font-weight: 400;
      line-height: 19.64px;
      color: #0078c9;
      cursor: pointer;
    }
    .chat-content-session-answer-operat-chose {
      // display: flex;
      display: none;
      cursor: pointer;
      > img {
        width: 14px;
      }
      > div {
        width: 1px;
        background-color: #bfc3c7;
        margin: 0 14px;
      }
    }
  }
  :deep(.vuepress-markdown-body) {
    padding: 5px;
  }
}
</style>
